package com.example.calculation;


import com.example.data.ImportDataFromFoodGame;
import com.example.entity.CalRecipe;
import com.example.entity.Chef;
import com.example.entity.Equip;
import com.example.entity.GlobalDate;
import com.example.entity.MyGameData;
import com.example.entity.OfficialGameData;
import com.example.entity.Recipe;
import com.example.utils.JSON;
import com.example.utils.MathExtend;
import com.example.utils.SortUtils;
import com.fasterxml.jackson.databind.node.ObjectNode;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletionService;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/***
 * 厨神大赛 厨师菜谱厨具组合得分推演
 */

public class GodInference{

    //用于第二阶段计算 的数据量
    final int topcount = 20000;

    //每个线程的任务大小
    final int segmentnums = 60;

    //多线程计算时，启用的线程数等于cpuu核心数减1， 最少为一个线程
    final int vCPU = Runtime.getRuntime().availableProcessors() > 2 ? Runtime.getRuntime().availableProcessors() - 1 : 1;

    // 使用的菜谱组合数
    final int playRecipeSubLength = 10000;

    //过滤规则
    final int chefMinRaritySum = 14;
    final int equipMinRarity = 2;
    final int equipMaxRarity = 2;

    //官方游戏数据
    private OfficialGameData officialGameData;

    //我的游戏数据
    private MyGameData myGameData;

    //修炼对全体厨师的影响
    private GlobalAddtion globalAddtion;

    public GlobalAddtion getGlobalAddtion() {
        return globalAddtion;
    }


    //厨师 菜谱大赛奖励倍数
    private double[] reward;
    private int[] materials;

    public double[] getReward() {
        return reward;
    }

    public void setReward(double[] reward) {
        this.reward = reward;
    }

    //拥有的厨师 菜谱 厨具
    private Chef[] ownChefs;
    private CalRecipe[] ownRecipes;
    private Equip[] ownEquips;

    private List<Recipe> tempOwnRecipes;

    private final String[] needType = {"Bake", "Steam", "Boil", "Fry", "Knife", "Stirfry", "UseKnife", "UseSteam", "UseBoil"
            , "UseBake", "UseStirfry", "UseFry", "UseFish", "UseCreation", "UseMeat", "UseVegetable"};

    //菜谱组合的排列结果
    List<List<PlayRecipe>> playRecipes = new ArrayList<List<PlayRecipe>>();

    //上场厨师的推理结果 （至少3星） (这个可以储存起来)
    int[][] playChefs;

    //使用厨具的推理结果 (售价，技法，金币收益) (这个可以储存起来)
    int[][] playEquips;

    //缓存一些和重复使用的结果
    TempCalCache tempCalCache;

    public GodInference(double[] rewardlsit, int[] materials) {


        this.reward = rewardlsit;
        this.materials = materials;

        //获取官方游戏数据
        ObjectNode jsonObject = ImportDataFromFoodGame.officalData("D:\\kaifa_environment\\code\\java\\native-image-learn\\src\\main\\resources\\foodgameData.json");

        OfficialGameData officialGameData = JSON.getObjectMapper().convertValue(jsonObject, OfficialGameData.class);
        officialGameData.buildMap();

        ObjectNode jsonObject1 = ImportDataFromFoodGame.IndividualData("D:\\kaifa_environment\\code\\java\\native-image-learn\\src\\main\\resources\\我拥有的厨师和菜谱.json",
                "D:\\kaifa_environment\\code\\java\\native-image-learn\\src\\main\\resources\\我拥有的厨具.json");


        //获取我的游戏数据
        MyGameData myGameData = MyGameData.importChefsAndRecipesFromFoodGame(officialGameData, jsonObject1);
        //初始化
        this.officialGameData = officialGameData;
        this.myGameData = myGameData;

        this.globalAddtion = new GlobalAddtion(myGameData.chefs.toArray(new Chef[0]), officialGameData.getSkills());

        //初始化我拥有的厨师，菜谱，厨具
        initOwn();

        modifyChefValue(ownChefs, globalAddtion);
        buildRecipeTags();

    }


    private static void modifyChefValue(Chef[] chefs, GlobalAddtion globalAddtion) {

        for (Chef chef : chefs) {

            chef.bake += globalAddtion.bake;
            chef.boil += globalAddtion.boil;
            chef.stirfry += globalAddtion.stirfry;
            chef.steam += globalAddtion.steam;
            chef.fry += globalAddtion.fry;
            chef.knife += globalAddtion.knife;

            List<Integer> tags = chef.getTags();
            if (tags != null && 0 < tags.size()) {
                for (Integer tag : tags) {
                    int value = 0;
                    if (tag == Chef.SEX_MAN) {
                        value = globalAddtion.manfill;
                    } else if (tag == Chef.SEX_WOMAN) {
                        value = globalAddtion.womanfill;
                    }
                    chef.bake += value;
                    chef.boil += value;
                    chef.stirfry += value;
                    chef.steam += value;
                    chef.fry += value;
                    chef.knife += value;
                }
            }
        }
    }

    public void buildCache() {
        buildIndex();

        TempCalCache.builder builder = new TempCalCache.builder();
        builder.init(ownChefs, ownRecipes, ownEquips, officialGameData, globalAddtion, reward);
        tempCalCache = builder.build();
    }


    public void buildIndex() {

        playRecipes.clear();
        //排列菜谱组合
        recipePermutation(1, new ArrayList<PlayRecipe>(9), new IngredientLimit(materials));

        // playRecipes = playRecipes.subList(0, playRecipeSubLength);


        System.out.println("有序菜谱组合数量" + playRecipes.size());

        CalRecipe[][] calRecipes = new CalRecipe[playRecipes.size()][9];

        // Integer中村的是  菜谱id和菜谱数量  高16位 菜谱数量 底16位 菜谱id
        // <CalRecipe.id,CalRecipe>
        Map<Integer, CalRecipe> maps = new HashMap<>(1000);


        for (int i = 0; i < playRecipes.size(); i++) {
            for (int i2 = 0; i2 < 9; i2++) {
                PlayRecipe playRecipe = playRecipes.get(i).get(i2);
                int count = playRecipe.getCount();
                int recepeid = playRecipe.getRecipe().getRecipeId();
                //之所以左移14位,是因为recepeid的大小不会超过16384
                int mapid = count << 14 | recepeid;
                CalRecipe calRecipe = maps.get(Integer.valueOf(mapid));
                if (calRecipe == null) {
                    calRecipe = new CalRecipe(playRecipe.getRecipe(), count);
                    maps.put(mapid, calRecipe);
                }
                calRecipes[i][i2] = calRecipe;
            }
        }
        System.out.println("菜谱种类 " + maps.size());
        compressionAndMapping(maps);

        ownRecipes = new CalRecipe[maps.size()];
        for (Map.Entry<Integer, CalRecipe> integerCalRecipeEntry : maps.entrySet()) {
            ownRecipes[integerCalRecipeEntry.getValue().index] = integerCalRecipeEntry.getValue();
        }

    }

    //将 CalRecipe中id映射成 从0开始的连续数组中
    public void compressionAndMapping(Map<Integer, CalRecipe> maps) {
        Set<Integer> integers = maps.keySet();
        Iterator<Integer> iterator = integers.iterator();
        int[] ints = new int[integers.size()];
        for (int i = 0; i < ints.length; i++) {
            ints[i] = iterator.next();
        }
        //将 ints 重新排序 ，安好从小到大
        SortUtils.quickSort(ints);
        // ints中 不会存在重复元素
        for (int i = 0; i < ints.length; i++) {
            CalRecipe calRecipe = maps.get(ints[i]);
            calRecipe.index = i;
        }
    }

    /**
     * 将设: 在没有厨具的情况下得分最高的，在带上厨具后仍然是最高的  虽然不一定，但很可能是一个比较优质的解
     */

    public void refere() {

        if (tempCalCache == null) {
            buildCache();
        }

        // 厨师，厨具排列组合
        buildPermutation();

        //定长线程池，线程数为(cpu-1)
        ExecutorService pool = new ThreadPoolExecutor(vCPU, vCPU,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());
        // ExecutorCompletionService 是 CompletionService 唯一实现类
        CompletionService<List<TopResult>> executorCompletionService = new ExecutorCompletionService<List<TopResult>>(pool);


        //厨师和菜谱 进行组合 ，计算得分，并存储起来
        long start, end;
        start = System.currentTimeMillis();

        //菜谱排列组合数
        int total = playRecipes.size();
        double t1 = total * 1.0 / segmentnums;

        //将任务划分成 groupnum 个组，每一组交给一个线程执行
        int groupnum = (int) ((int) t1 < t1 ? t1 + 1 : t1);

        // 将 playRecipes和 playChefs改成二维数组， 访问速度会快点
        int[][] playRecipes2 = new int[playRecipes.size()][9];

        HashMap<Integer, Integer> idToIndex = new HashMap<>(500);
        for (int i = 0; i < ownRecipes.length; i++) {
            idToIndex.put(ownRecipes[i].recipeId, ownRecipes[i].index);
        }

        for (int i = 0; i < playRecipes.size(); i++) {
            List<PlayRecipe> playRecipes = this.playRecipes.get(i);
            for (int i2 = 0; i2 < 9; i2++) {
                playRecipes2[i][i2] = idToIndex.get(playRecipes.get(i2).getRecipe().getRecipeId());
            }
        }

        //用于接收线程返回的数据
        List<TopResult> topPlayChers = new LinkedList<TopResult>();

        int[][] scoreNoEquip = tempCalCache.socreCacheNoEquip;


        AtomicInteger minMax = new AtomicInteger(0);


        for (int i = 0; i < groupnum; i++) {
            ChefAndRecipeThread chefAndRecipeThread;
            if (i == groupnum - 1) {
                chefAndRecipeThread = new ChefAndRecipeThread(playRecipes2, playChefs, scoreNoEquip, i * segmentnums, total, minMax);
            } else {
                chefAndRecipeThread = new ChefAndRecipeThread(playRecipes2, playChefs, scoreNoEquip, i * segmentnums, (i + 1) * segmentnums, minMax);
            }
            executorCompletionService.submit(chefAndRecipeThread);
        }
        try {
            for (int i = 0; i < groupnum; i++) {
                List<TopResult> topResults = executorCompletionService.take().get();
                topPlayChers.addAll(topResults);
                if (topPlayChers.size() > topcount) {
                    Collections.sort(topPlayChers, new Comparator<TopResult>(){
                        @Override
                        public int compare(TopResult o1, TopResult o2) {
                            return o2.score - o1.score;
                        }
                    });
                    List<TopResult> topResults1 = topPlayChers.subList(topcount, topPlayChers.size());

                    int globalMinMax = topPlayChers.get(topPlayChers.size() - 1).score;
                    int curminmax;

                    do {
                        curminmax = minMax.get();
                    } while ((globalMinMax > curminmax) && minMax.compareAndSet(0, globalMinMax));

                    topResults1.clear();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        //将所有线程的计算结果存入一起， 并从高到底排序，取前1000个结果
        Collections.sort(topPlayChers, new Comparator<TopResult>(){
            @Override
            public int compare(TopResult o1, TopResult o2) {
                return o2.score - o1.score;
            }
        });
        if (topPlayChers.size() > topcount) {
            topPlayChers.subList(topcount, topPlayChers.size()).clear();
        }
        end = System.currentTimeMillis();
        System.out.println("全菜谱 全厨师 无厨具排列结果用时:" + (end - start) + "ms");


        calSecondStage(topPlayChers);


        pool.shutdown();
    }


    /**
     * 第二阶段的计算
     */
    private void calSecondStage(List<TopResult> topPlayChers) {
        //记录最高得分
        final AtomicInteger max = new AtomicInteger(0);

        TopResult[] topResults = topPlayChers.toArray(new TopResult[0]);

        //将任务分成vCPU份，正好一个线程执行一部分
        int segement = topResults.length / vCPU;


        ExecutorService pool = new ThreadPoolExecutor(vCPU, vCPU,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());


        ChefEquipRecipeThread chefAndRecipeThread;

        CountDownLatch countDownLatch = new CountDownLatch(vCPU);

        GlobalDate globalDate = new GlobalDate();
        globalDate.ownChefs = ownChefs;
        globalDate.ownEquips = ownEquips;
        globalDate.ownRecipes = ownRecipes;
        globalDate.tempCalCache = tempCalCache;


        if (vCPU > 1) {
            for (int i = 0; i < (vCPU - 1); i++) {
                chefAndRecipeThread = new ChefEquipRecipeThread(playEquips, topResults, max, globalDate, countDownLatch, i * segement, (i + 1) * segement);
                pool.submit(chefAndRecipeThread);
            }
            chefAndRecipeThread = new ChefEquipRecipeThread(playEquips, topResults, max, globalDate, countDownLatch, (vCPU - 1) * segement, topResults.length);
            pool.submit(chefAndRecipeThread);
        } else {
            chefAndRecipeThread = new ChefEquipRecipeThread(playEquips, topResults, max, globalDate, countDownLatch, 0, topResults.length);
            pool.submit(chefAndRecipeThread);
        }

        try {
            countDownLatch.await();
            ;
        } catch (Exception e) {
            e.printStackTrace();
        }

        pool.shutdown();
        System.out.println("。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。");
        System.out.println("可能的最高分组合为" + max.get());
    }


    //排列厨师和厨具
    public void buildPermutation() {

        playChefs = null;
        playEquips = null;

        //排列厨师组合 (挑出三个)  因为菜谱为全排列结果    所以 厨师按照顺序挑选三个，（顺序在前的厨师，排列必定也在前）
        playChefs = chefsPermutation();
        System.out.println("厨师组合数" + playChefs.length);


        //厨具排列结果  为了减少计算量  排除了一星厨具
        playEquips = equipspermute();
        System.out.println("厨具组合数" + playEquips.length / 3);

    }


    //从所有已拥有厨师中 挑选3个 的所有组合情况(按照星级降序排列，从头挑选)
    private int[][] chefsPermutation() {
        int length = ownChefs.length;
        if (length < 3) {
            return null;
        }
        int[][] result;
        List<int[]> temp = new ArrayList<int[]>(5000);
        for (int i = 0; i < length; i++) {
            for (int j = i + 1; j < length; j++) {
                for (int k = j + 1; k < length; k++) {
                    //排列过程中 进行一些过滤 排除三个厨师全为3星的结果，排除两个3星 一个4星的结果
                    if (ownChefs[i].rarity + ownChefs[j].rarity + ownChefs[k].rarity >= chefMinRaritySum) {
                        int[] s = new int[3];
                        s[0] = ownChefs[i].index;
                        s[1] = ownChefs[j].index;
                        s[2] = ownChefs[k].index;
                        temp.add(s);
                    }
                }
            }
        }
        result = temp.toArray(new int[0][0]);
        return result;
    }

    private int[][] equipspermute() {
        int length = ownEquips.length;
        // result中结果是用一维数组，表示二维形式， 列入 (0，1，2)代表第一组排列 (3,4,5)代表第二组排列
        if (length < 3) {
            return null;
        }
        int totalLength = MathExtend.A(length, 3);

        int[][] result = new int[totalLength][3];
        int position = 0;
        for (int i = 0; i < length; ++i) {
            for (int i2 = 0; i2 < length; i2++) {
                if (i2 == i) {
                    continue;
                }
                for (int i3 = 0; i3 < length; i3++) {
                    if (i3 == i || i3 == i2) {
                        continue;
                    }
                    result[position][0] = ownEquips[i].index;
                    result[position][1] = ownEquips[i2].index;
                    result[position++][2] = ownEquips[i3].index;

                }
            }
        }
        return result;
    }

    int[] deepLimit = {0, 4, 3, 2, 2, 2, 2, 2, 2, 3};

    //从所有已拥有菜谱中挑选出9个 的组合情况（按照价格降序排列，从头挑选, 菜的份数，先挑选的先满足）
    private void recipePermutation(int index, List<PlayRecipe> play, IngredientLimit ingredientLimit) {
        if (index == 10) {
            playRecipes.add(play);
            return;
        }
        int limit = deepLimit[index];
        //剩余食材数
        int[] finalMaterialCount = ingredientLimit.getFinalMaterialCount();

        //各个菜 可以做多少份
        int[] integerIntegerMap = calQuantity(finalMaterialCount);
        //总价排序 影响ownRecipes集合
        SortOfprice(integerIntegerMap, tempOwnRecipes);


        for (int i = 0; i < limit; i++) {

            //记录所选菜谱和当前食材量
            Recipe selectRecipe = tempOwnRecipes.remove(0);

            int quantity = integerIntegerMap[selectRecipe.getRecipeId()];

            if (quantity > 0) {

                List<PlayRecipe> newplay = new ArrayList<>(9);

                newplay.addAll(play);
                //计算 做一定份数菜后，食材剩余量
                ingredientLimit.cookingQuanttiy(selectRecipe, quantity);
                //食材余量备份
                int[] clone = ingredientLimit.getFinalMaterialCount();

                //将新选的菜放入其中
                PlayRecipe p = new PlayRecipe(selectRecipe, quantity);
                newplay.add(p);

                recipePermutation(index + 1, newplay, ingredientLimit);

                //还原食材数量
                ingredientLimit.setMaterialCount(clone);

            }
            //还原菜谱
            tempOwnRecipes.add(selectRecipe);
        }

    }

    int[] counts = new int[7000];

    /**
     * @param materialCount 各种食材的剩余数量,数组下标对应食材的id
     */
    //计算每个菜可以做多少份
    private int[] calQuantity(int[] materialCount) {
        int[] maxequiplimit = globalAddtion.getMaxequiplimit();
        int length = tempOwnRecipes.size();
        Recipe ownRecipe;
        for (int i = 0; i < length; i++) {
            ownRecipe = tempOwnRecipes.get(i);
            int count = IngredientLimit.cookingQuanttiy(ownRecipe.materials2, ownRecipe.getLimit() + maxequiplimit[ownRecipe.getRarity()], materialCount);
            counts[ownRecipe.getRecipeId()] = count;
        }
        return counts;
    }

    int[] prices = new int[7000];

    //按照最高份数的价格排序  大到小
    private void SortOfprice(int[] quanttiy, List<Recipe> recipes) {

        for (Recipe ownRecipe : recipes) {
            double rew = reward[ownRecipe.getRecipeId()];
            prices[ownRecipe.getRecipeId()] = (int) Math.ceil(ownRecipe.getPrice() * (1 + rew) * quanttiy[ownRecipe.getRecipeId()]);

        }
        Collections.sort(recipes, new Comparator<Recipe>(){
            @Override
            public int compare(Recipe r1, Recipe r2) {
                return prices[r2.getRecipeId()] - prices[r1.getRecipeId()];
            }
        });
    }


    // 菜 面 肉 鱼
    private String[] materialTag = {"留空",
            "肉", "鱼", "肉", "肉", "肉", "菜", "肉", "肉", "肉", "菜",
            "面", "肉", "菜", "菜", "菜", "菜", "菜", "菜", "菜", "面",
            "面", "菜", "菜", "鱼", "菜", "肉", "肉", "肉", "面", "菜",
            "菜", "鱼", "菜", "面", "面", "菜", "鱼", "肉", "肉", "肉",
            "鱼", "鱼", "肉", "肉", "菜", "菜"
    };

    // 给菜谱添加标签 ，  需要哪一类的食材
    private void buildRecipeTags() {

        for (Recipe ownRecipe : tempOwnRecipes) {
            List<Recipe.Material> materials = ownRecipe.getMaterials();
            int[] tags = {0, 0, 0, 0};
            for (Recipe.Material material : materials) {
                switch (materialTag[material.getMaterial()]) {
                    case "面":
                        tags[0] = 1;
                        break;
                    case "肉":
                        tags[1] = 1;
                        break;
                    case "菜":
                        tags[2] = 1;
                        break;
                    case "鱼":
                        tags[3] = 1;
                        break;
                    default:
                        break;
                }
            }
            ownRecipe.setTags(tags);
        }
    }


    // 确定拥有那些菜谱 厨师，厨具
    private void initOwn() {
        List<Chef> chefs;

        //过滤掉一二星厨师
        chefs = myGameData.chefs.stream().filter(new Predicate<Chef>(){
            @Override
            public boolean test(Chef chef) {
                if (chef.getRarity() >= 3) {
                    return true;
                }
                return false;
            }
        }).sorted(new Comparator<Chef>(){
            @Override
            public int compare(Chef o1, Chef o2) {
                return o2.getRarity() - o1.getRarity();
            }
        }).collect(Collectors.toList());

        ownChefs = chefs.toArray(new Chef[0]);


        List<Equip> tempEquips = new ArrayList<Equip>(100);

        List<Equip> equips = myGameData.equips;
        equips = new ArrayList<>(equips);
        //过滤掉采集出局  减售价厨具
        for (Equip equip : equips) {

            if (equip.getOrigin().contains("新手奖池") && equip.getRarity() >= equipMinRarity && equip.getRarity() <= equipMaxRarity) {
                tempEquips.add(equip);
                tempEquips.add(equip);
            } else if (equip.getOrigin().contains("中级奖池") && equip.getRarity() >= equipMinRarity && equip.getRarity() <= equipMaxRarity) {
                tempEquips.add(equip);
            } else if (!equip.getOrigin().contains("璧池") && !equip.getOrigin().contains("限时礼包") && !equip.getOrigin().contains("圣诞奖池") && !equip.getOrigin().contains("有生之年池")) {
                if (equip.getRarity() >= equipMinRarity && equip.getRarity() <= equipMaxRarity) {
                    tempEquips.add(equip);
                }
            }

        }
        ownEquips = tempEquips.toArray(new Equip[0]);

        tempOwnRecipes = myGameData.recipes;


        for (Recipe tempOwnRecipe : tempOwnRecipes) {
            tempOwnRecipe.materials2 = tempOwnRecipe.materials.toArray(new Recipe.Material[]{});
        }
    }

}
